//! Configuration models
//! 
//! This module defines configuration structures for test cases and steps,
//! following HttpRunner's configuration design patterns.

use crate::error::{Error, Result, ValidationError};
use crate::models::{Value, ValueMap, StringMap, Validatable};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::time::Duration;

/// Test case configuration (corresponds to HttpRunner's TConfig)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TestConfig {
    /// Test case name (required)
    pub name: String,
    
    /// Whether to verify SSL certificates
    #[serde(default)]
    pub verify: bool,
    
    /// Base URL for requests
    pub base_url: Option<String>,
    
    /// Global request headers
    #[serde(default)]
    pub headers: StringMap,
    
    /// Environment variables
    #[serde(default)]
    pub environs: StringMap,
    
    /// Test case variables
    #[serde(default)]
    pub variables: ValueMap,
    
    /// Original user variables (before env merge, not serialized)
    #[serde(skip)]
    pub original_variables: ValueMap,
    
    /// Test parameters for data-driven testing
    #[serde(default)]
    pub parameters: ValueMap,
    
    /// Parameters configuration
    pub parameters_setting: Option<ParametersConfig>,
    
    /// Think time configuration
    pub think_time_setting: Option<ThinkTimeConfig>,
    
    /// WebSocket configuration
    pub websocket_setting: Option<WebSocketConfig>,
    
    /// Plugin configuration
    pub plugin_setting: Option<PluginConfig>,
    
    /// AI service options
    pub ai_options: Option<AIServiceOptions>,
    
    /// Request timeout in seconds
    pub request_timeout: Option<f32>,
    
    /// Test case timeout in seconds
    pub case_timeout: Option<f32>,
    
    /// Variables to export after test execution
    #[serde(default)]
    pub export: Vec<String>,
    
    /// Test case weight for load testing
    #[serde(default = "default_weight")]
    pub weight: i32,
    
    /// Test case file path
    pub path: Option<String>,
    
    /// MCP (Model Context Protocol) configuration path
    pub mcp_config_path: Option<String>,
    
    /// Global anti-risk switch
    #[serde(default)]
    pub anti_risk: bool,
    
    /// Enable auto popup handler
    #[serde(default)]
    pub auto_popup_handler: bool,
}

/// Step configuration (corresponds to HttpRunner's StepConfig)
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct StepConfig {
    /// Step name (required)
    pub step_name: String,
    
    /// Step variables
    #[serde(default)]
    pub variables: ValueMap,
    
    /// Step parameters
    #[serde(default)]
    pub parameters: ValueMap,
    
    /// Parameters configuration
    pub parameters_setting: Option<ParametersConfig>,
    
    /// Setup hooks to run before step execution
    #[serde(default)]
    pub setup_hooks: Vec<String>,
    
    /// Teardown hooks to run after step execution
    #[serde(default)]
    pub teardown_hooks: Vec<String>,
    
    /// Variable extraction rules
    #[serde(default)]
    pub extract: StringMap,
    
    /// Validation rules
    #[serde(default)]
    pub validators: Vec<Value>,
    
    /// Variables to export after step execution
    #[serde(default)]
    pub export: Vec<String>,
    
    /// Number of times to loop this step
    pub loops: Option<i32>,
    
    /// Enable auto popup handler for this step
    #[serde(default)]
    pub auto_popup_handler: bool,
}

/// Parameters configuration for data-driven testing
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ParametersConfig {
    /// Parameter strategy
    pub strategy: ParameterStrategy,
    
    /// Whether to pick parameters randomly
    #[serde(default)]
    pub random: bool,
    
    /// Maximum number of parameter combinations to use
    pub limit: Option<usize>,
}

/// Parameter combination strategies
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ParameterStrategy {
    /// Cartesian product of all parameters
    Cartesian,
    
    /// Pairwise combination
    Pairwise,
    
    /// Sequential combination
    Sequential,
    
    /// Custom strategy
    Custom(String),
}

/// Think time configuration (corresponds to HttpRunner's ThinkTimeConfig)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ThinkTimeConfig {
    /// Think time strategy
    pub strategy: ThinkTimeStrategy,
    
    /// Strategy-specific settings
    pub setting: Option<Value>,
    
    /// Maximum think time limit in seconds
    pub limit: Option<f64>,
}

/// Think time strategies
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ThinkTimeStrategy {
    /// Use recorded think time as-is
    Default,
    
    /// Use random percentage of recorded think time
    RandomPercentage,
    
    /// Multiply recorded think time by a factor
    Multiply,
    
    /// Ignore think time
    Ignore,
}

/// WebSocket configuration (corresponds to HttpRunner's WebSocketConfig)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WebSocketConfig {
    /// Number of reconnection attempts
    #[serde(default = "default_reconnection_times")]
    pub reconnection_times: i64,
    
    /// Reconnection interval in milliseconds
    #[serde(default = "default_reconnection_interval")]
    pub reconnection_interval: i64,
    
    /// Maximum message size in bytes
    #[serde(default = "default_max_message_size")]
    pub max_message_size: i64,
    
    /// Connection timeout in seconds
    pub timeout: Option<f64>,
}

/// Plugin configuration (corresponds to HttpRunner's PluginConfig)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PluginConfig {
    /// Plugin file path
    pub path: Option<String>,
    
    /// Plugin type (bin, so, py, js, wasm)
    pub plugin_type: Option<String>,
    
    /// Plugin content (for inline plugins)
    #[serde(skip)]
    pub content: Option<Vec<u8>>,
    
    /// Plugin configuration parameters
    #[serde(default)]
    pub config: ValueMap,
}

/// AI service options
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AIServiceOptions {
    /// LLM service type
    pub llm_service: Option<String>,
    
    /// Computer vision service type
    pub cv_service: Option<String>,
    
    /// AI model configuration
    #[serde(default)]
    pub model_config: ValueMap,
    
    /// API key for AI services
    pub api_key: Option<String>,
    
    /// API endpoint URL
    pub endpoint: Option<String>,
    
    /// Request timeout for AI services
    pub timeout: Option<f64>,
}

impl TestConfig {
    /// Create a new test configuration
    pub fn new(name: String) -> Self {
        Self {
            name,
            verify: false,
            base_url: None,
            headers: HashMap::new(),
            environs: HashMap::new(),
            variables: HashMap::new(),
            original_variables: HashMap::new(),
            parameters: HashMap::new(),
            parameters_setting: None,
            think_time_setting: None,
            websocket_setting: None,
            plugin_setting: None,
            ai_options: None,
            request_timeout: None,
            case_timeout: None,
            export: Vec::new(),
            weight: default_weight(),
            path: None,
            mcp_config_path: None,
            anti_risk: false,
            auto_popup_handler: false,
        }
    }
    
    /// Set base URL
    pub fn with_base_url(mut self, base_url: String) -> Self {
        self.base_url = Some(base_url);
        self
    }
    
    /// Set headers
    pub fn with_headers(mut self, headers: StringMap) -> Self {
        self.headers = headers;
        self
    }
    
    /// Set variables
    pub fn with_variables(mut self, variables: ValueMap) -> Self {
        self.variables = variables;
        self
    }
    
    /// Set request timeout
    pub fn with_request_timeout(mut self, timeout: f32) -> Self {
        self.request_timeout = Some(timeout);
        self
    }
    
    /// Set case timeout
    pub fn with_case_timeout(mut self, timeout: f32) -> Self {
        self.case_timeout = Some(timeout);
        self
    }
    
    /// Enable SSL verification
    pub fn with_ssl_verify(mut self, verify: bool) -> Self {
        self.verify = verify;
        self
    }
    
    /// Merge environment variables with base_url
    pub fn merge_environs(&mut self) {
        // Priority: env base_url > base_url
        if let Some(base_url) = &self.base_url {
            if !self.environs.contains_key("base_url") {
                self.environs.insert("base_url".to_string(), base_url.clone());
            }
        }
        
        // Merge config variables with environment variables
        // Priority: env > config variables
        for (k, v) in &self.environs {
            self.variables.insert(k.clone(), Value::String(v.clone()));
        }
    }
}

impl StepConfig {
    /// Create a new step configuration
    pub fn new(step_name: String) -> Self {
        Self {
            step_name,
            variables: HashMap::new(),
            parameters: HashMap::new(),
            parameters_setting: None,
            setup_hooks: Vec::new(),
            teardown_hooks: Vec::new(),
            extract: HashMap::new(),
            validators: Vec::new(),
            export: Vec::new(),
            loops: None,
            auto_popup_handler: false,
        }
    }
    
    /// Set variables
    pub fn with_variables(mut self, variables: ValueMap) -> Self {
        self.variables = variables;
        self
    }
    
    /// Set validators
    pub fn with_validators(mut self, validators: Vec<Value>) -> Self {
        self.validators = validators;
        self
    }
    
    /// Set extraction rules
    pub fn with_extract(mut self, extract: StringMap) -> Self {
        self.extract = extract;
        self
    }
}

impl Validatable for TestConfig {
    fn validate(&self) -> Result<()> {
        if self.name.is_empty() {
            return Err(Error::Validation(ValidationError::FieldValidation {
                field: "name".to_string(),
                reason: "Test case name cannot be empty".to_string(),
            }));
        }
        
        if let Some(timeout) = self.request_timeout {
            if timeout <= 0.0 {
                return Err(Error::Validation(ValidationError::RangeValidation {
                    field: "request_timeout".to_string(),
                    min: "0".to_string(),
                    max: "∞".to_string(),
                }));
            }
        }
        
        if let Some(timeout) = self.case_timeout {
            if timeout <= 0.0 {
                return Err(Error::Validation(ValidationError::RangeValidation {
                    field: "case_timeout".to_string(),
                    min: "0".to_string(),
                    max: "∞".to_string(),
                }));
            }
        }
        
        Ok(())
    }
}

impl Validatable for StepConfig {
    fn validate(&self) -> Result<()> {
        if self.step_name.is_empty() {
            return Err(Error::Validation(ValidationError::FieldValidation {
                field: "step_name".to_string(),
                reason: "Step name cannot be empty".to_string(),
            }));
        }
        
        if let Some(loops) = self.loops {
            if loops <= 0 {
                return Err(Error::Validation(ValidationError::RangeValidation {
                    field: "loops".to_string(),
                    min: "1".to_string(),
                    max: "∞".to_string(),
                }));
            }
        }
        
        Ok(())
    }
}

impl Default for ParameterStrategy {
    fn default() -> Self {
        ParameterStrategy::Cartesian
    }
}

impl Default for ThinkTimeStrategy {
    fn default() -> Self {
        ThinkTimeStrategy::Default
    }
}

// Default value functions
fn default_weight() -> i32 {
    1
}

fn default_reconnection_times() -> i64 {
    3
}

fn default_reconnection_interval() -> i64 {
    1000
}

fn default_max_message_size() -> i64 {
    1024 * 1024 // 1MB
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_test_config_creation() {
        let config = TestConfig::new("test_config".to_string());
        assert_eq!(config.name, "test_config");
        assert_eq!(config.weight, 1);
        assert!(!config.verify);
    }

    #[test]
    fn test_test_config_builder() {
        let config = TestConfig::new("test".to_string())
            .with_base_url("https://api.example.com".to_string())
            .with_request_timeout(30.0)
            .with_ssl_verify(true);
        
        assert_eq!(config.base_url, Some("https://api.example.com".to_string()));
        assert_eq!(config.request_timeout, Some(30.0));
        assert!(config.verify);
    }

    #[test]
    fn test_config_validation() {
        let config = TestConfig::new("".to_string());
        assert!(config.validate().is_err());
        
        let config = TestConfig::new("valid_name".to_string());
        assert!(config.validate().is_ok());
    }

    #[test]
    fn test_step_config_creation() {
        let config = StepConfig::new("test_step".to_string());
        assert_eq!(config.step_name, "test_step");
        assert!(config.variables.is_empty());
        assert!(config.validators.is_empty());
    }
}
